<?php
/**
 * Created by PhpStorm.
 * User: Hong
 * Date: 2018/3/9
 * Time: 17:09
 * Function:
 */

namespace Platform\System\Tools\Permission;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Container\Container as App;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;

abstract class PermissionsImporter
{
    /**
     * @var App
     */
    private $app;

    /**
     * @var Model
     */
    protected $model;

    /**
     * @var \Illuminate\Routing\RouteCollection
     */
    protected $routes;

    /**
     * @var string
     */
    protected $filterNamespace;

    /**
     * @var string
     */
    protected $moduleTag;

    /**
     * @var string
     */
    protected $permissionTag;


    /**
     * Importer constructor.
     * @param App $app
     * @param Router $router
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function __construct(App $app,Router $router)
    {
        $this->app = $app;
        $this->makeModel();
        $this->filterNamespace = config('system.permission.'.$this->filterNamespace.'.namespace');

        $this->routes = $router->getRoutes();
        $this->moduleTag = '@module';
        $this->permissionTag = '@permission';
    }

    /**
     * 获得权限模型
     * @return mixed
     */
    abstract function model();

    /**
     * 创建模型对象
     * @return Model|mixed
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function makeModel() {
        $model = $this->app->make($this->model());

        if (!$model instanceof Model){
            myResult()->stop(500,"Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model");
        }

        return $this->model = $model;
    }

    /**
     * 导入权限
     * @throws \ReflectionException
     */
    public function handle()
    {
        $existAction = $this->model->all()->pluck('action')->toArray();

        $scan = $this->getAllPermissions();
        $permissions = [];
        $actions = [];
        foreach ($scan as &$permission) {
            $action = implode('@', [$permission['controller'], $permission['action']]);
            $permissions[$action] = $permission;

            $actions[] = $action;
        }

        /// 删除权限
        $actionsToDel = array_diff($existAction, $actions);
        $this->deletePermissions($actionsToDel);

        /// 导入权限
        $this->importPermission($permissions);
    }

    /**
     * 写入数据库
     * @param array $permissions
     */
    protected function importPermission(array $permissions)
    {
        foreach ($permissions as $action => $permission) {
            $this->model->updateOrCreate([
                'action' => $action
            ], [
                'module' => $permission['module'],
                'name' => $permission['name'],
                'method' => $permission['method'],
                'uri' => $permission['uri'],
                'action' => $action,
                'route_name' => $permission['route_name']
            ]);

            $str = "导入或刷新权限 [{$permission['module']}] - [{$permission['name']}]";
        }
    }

    /**
     *
     * @param array $permissions
     */
    protected function deletePermissions(array $permissions)
    {
        foreach ($permissions as $item) {
            $permissionToDel = $this->model->where('action', $item)->first();
//            $permissionToDel->roles()->detach();
            $permissionToDel->departments()->detach();
            $str = "删除权限 [{$permissionToDel['module']}] - [{$permissionToDel['name']}]";
            $permissionToDel->delete();

        }
    }

    /**
     * @return array
     * @throws \ReflectionException
     */
    public function getAllPermissions()
    {
        $routes = $this->getRoutes();
        $res = [];
        foreach ($routes as $route) {

            $ref = new \ReflectionClass($route['controller']);
            $classDoc = $ref->getDocComment();
            if ($classDoc != false && $module = $this->getModule($classDoc)) {
                if ($ref->hasMethod($route['action'])) {
                    $methodDoc = $ref->getMethod($route['action'])->getDocComment();
                    if ($methodDoc != false && ($name = $this->getPermission($methodDoc))) {
                        ///
                        $permission = $route;
                        $permission['module'] = $module;
                        $permission['name'] = $name;

                        ///
                        $res[] = $permission;
                    }
                }
            }
        }

        return $res;
    }

    /**
     * @param $doc
     * @return null|string
     */
    protected function getModule($doc)
    {
        return $this->getTag($doc, $this->moduleTag);
    }

    /**
     * @param $doc
     * @return null|string
     */
    protected function getPermission($doc)
    {
        return $this->getTag($doc, $this->permissionTag);
    }

    /**
     * @return array
     */
    protected function getRoutes()
    {
        $routes = collect($this->routes)->filter(function (Route $route) {
            return starts_with($route->getActionName(), $this->filterNamespace);
        })->map(function (Route $route) {
            $item = [
                'method' => implode('|', $route->methods()),
                'uri'    => $route->uri(),
                'route_name'   => $route->getName(),
            ];

            $ac = $this->getControllerAndAction(ltrim($route->getActionName(), '\\'));

            return array_merge($item, $ac);
        })->all();

        return $routes;
    }

    /**
     * @param $actionName
     * @return array
     */
    protected function getControllerAndAction($actionName)
    {
        list($class, $action) = explode('@', $actionName);

        return ['controller' => $class, 'action' => $action];
    }

    /**
     * @param $str
     * @param string $tag
     * @return null|string
     */
    protected function getTag($str, $tag = '')
    {
        if (empty($tag))
            return null;

        //
        $matches = array();
        if (preg_match("/" . $tag . "(.*)(\\r\\n|\\r|\\n)/U", $str, $matches)) {
            return trim($matches[1]);
        }

        //
        return null;
    }
}